<!DOCTYPE html>
<!--
Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/core/test_utils.html">
<link rel="import" href="/tracing/metrics/spa_navigation_metric.html">
<link rel="import" href="/tracing/value/histogram_set.html">

<script>
'use strict';

tr.b.unittest.testSuite(function() {
  const RENDERER_PROCESS_ID = 1234;
  const RENDERER_PROCESS_MAIN_THREAD_ID = 1;
  const BROWSER_PROCESS_ID = 1;
  const BROWSER_PROCESS_MAIN_THREAD_ID = 12;
  const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION =
      'spaNavigationStartToFpDuration';

  function createChromeProcessesOnModel(model) {
    const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
    const mainThread = rendererProcess.getOrCreateThread(
        RENDERER_PROCESS_MAIN_THREAD_ID);
    mainThread.name = 'CrRendererMain';
    const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
    const browserMainThread = browserProcess.getOrCreateThread(
        BROWSER_PROCESS_MAIN_THREAD_ID);
    browserMainThread.name = 'CrBrowserMain';
  }

  function addSpaNavigationEvent(model, timestamp, args) {
    const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
    const mainThread = rendererProcess.getOrCreateThread(
        RENDERER_PROCESS_MAIN_THREAD_ID);

    mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
      cat: 'blink',
      title: 'FrameLoader::updateForSameDocumentNavigation',
      start: timestamp,
      duration: 0.1,
      args
    }));
  }

  function addGoToIndexSlice(model, timestamp) {
    const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
    const browserMainThread = browserProcess.getOrCreateThread(
        BROWSER_PROCESS_MAIN_THREAD_ID);
    browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
      cat: 'browser,navigation,benchmark',
      title: 'NavigationControllerImpl::GoToIndex',
      start: timestamp,
      duration: 0.1
    }));
  }

  function addFirstPaintSlice(model, timestamp) {
    const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
    const mainThread = rendererProcess.getOrCreateThread(
        RENDERER_PROCESS_MAIN_THREAD_ID);

    mainThread.sliceGroup.pushSlice(tr.c.TestUtils.newSliceEx({
      cat: 'blink',
      title: 'PaintLayerCompositor::updateIfNeededRecursive',
      start: timestamp,
      duration: 0.1
    }));
  }

  function addInputLatencyRelatedSlices(model, timestamp) {
    const latencyInfoFlowSlice = tr.c.TestUtils.newSliceEx({
      cat: 'input,benchmark',
      title: 'LatencyInfo.Flow',
      start: timestamp - 2,
      duration: 0.1,
      bindId: '0x600000057',
      args: {step: 'handleInputEventMain'}
    });
    const handleInputEventSlice = tr.c.TestUtils.newSliceEx({
      cat: 'blink,rail',
      title: 'WebViewImpl::handleInputEvent',
      start: timestamp - 1, // Assume handleInputEvent always delays 1ms.
      duration: 0.1,
      args: {type: 'MouseUp'}
    });

    const rendererProcess = model.getOrCreateProcess(RENDERER_PROCESS_ID);
    const mainThread = rendererProcess.getOrCreateThread(
        RENDERER_PROCESS_MAIN_THREAD_ID);

    handleInputEventSlice.parentSlice = latencyInfoFlowSlice;
    mainThread.sliceGroup.pushSlice(latencyInfoFlowSlice);
    mainThread.sliceGroup.pushSlice(handleInputEventSlice);

    const browserProcess = model.getOrCreateProcess(BROWSER_PROCESS_ID);
    const browserMainThread = browserProcess.getOrCreateThread(
        BROWSER_PROCESS_MAIN_THREAD_ID);
    browserMainThread.sliceGroup.pushSlice(tr.c.TestUtils.newAsyncSliceEx({
      cat: 'benchmark,latencyInfo,rail',
      title: 'InputLatency::MouseUp',
      start: timestamp - 3,
      duration: 0.1,
      args: {
        data: {
          trace_id: 25769803863,
          INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT: {
            time: timestamp - 3 + 0.1
          }
        }
      }
    }));
  }

  function getHistogramNamed(name, model) {
    const histograms = new tr.v.HistogramSet();
    tr.metrics.spaNavigationMetric(histograms, model);
    const spaNavigationStartToFpDurationHist = histograms.getHistogramNamed(
        name);
    return spaNavigationStartToFpDurationHist;
  }

  test('spaNavStartToFirstPaintDuration_noChromeProcess', function() {
    const model = tr.c.TestUtils.newModel();
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert(!histogram);
  });

  test('spaNavStartToFirstPaintDuration_noSpaNavStart', function() {
    const model = tr.c.TestUtils.newModel(model => {
      createChromeProcessesOnModel(model);
      addSpaNavigationEvent(model, 100);
      addFirstPaintSlice(model, 101);
    });
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert.strictEqual(0, histogram.numValues);
  });

  test('spaNavStartToFirstPaintDuration_noFirstPaint', function() {
    const model = tr.c.TestUtils.newModel(model => {
      createChromeProcessesOnModel(model);
      addInputLatencyRelatedSlices(model, 99);
      addSpaNavigationEvent(model, 100);
    });
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert.strictEqual(0, histogram.numValues);
  });

  test('spaNavStartToFirstPaintDuration_inputLatencyAsNavStart', function() {
    const URL = 'https://11111';
    const model = tr.c.TestUtils.newModel(model => {
      createChromeProcessesOnModel(model);
      addInputLatencyRelatedSlices(model, 99);
      addSpaNavigationEvent(model, 100, {url: URL});
      addFirstPaintSlice(model, 101);
    });
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert.strictEqual(1, histogram.running.count);
    const navStartTs = model.convertTimestampToModelTime(
        'traceEventClock', 99 - 3 + 0.1);
    const expectedDuration = 101 - navStartTs;
    assert.closeTo(expectedDuration, histogram.running.sum, 0.5);

    const binsWithSampleDiagnosticMaps =
        histogram.allBins.filter(bin => bin.diagnosticMaps.length > 0);
    const diagnostic = tr.b.getOnlyElement(binsWithSampleDiagnosticMaps[0]
        .diagnosticMaps[0].get('Navigation infos'));
    assert.strictEqual(diagnostic.url, URL);
    assert.strictEqual(diagnostic.pid, RENDERER_PROCESS_ID);
    assert.strictEqual(diagnostic.navStart, navStartTs);
    assert.strictEqual(diagnostic.firstPaint, 101);
  });

  test('spaNavStartToFirstPaintDuration_goToIndexAsNavStart', function() {
    const URL = 'https://11111';
    const model = tr.c.TestUtils.newModel(model => {
      createChromeProcessesOnModel(model);
      addGoToIndexSlice(model, 99);
      addSpaNavigationEvent(model, 100, {url: URL});
      addFirstPaintSlice(model, 101);
    });
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert.strictEqual(1, histogram.running.count);
    const expectedDuration = 101 - 99;
    assert.closeTo(expectedDuration, histogram.running.sum, 0.5);

    const binsWithSampleDiagnosticMaps =
        histogram.allBins.filter(bin => bin.diagnosticMaps.length > 0);
    const diagnostic = tr.b.getOnlyElement(binsWithSampleDiagnosticMaps[0]
        .diagnosticMaps[0].get('Navigation infos'));
    assert.strictEqual(diagnostic.url, URL);
    assert.strictEqual(diagnostic.pid, RENDERER_PROCESS_ID);
    assert.strictEqual(diagnostic.navStart, 99);
    assert.strictEqual(diagnostic.firstPaint, 101);
  });

  test('spaNavStartToFirstPaintDuration_multipleSpaNavs', function() {
    const model = tr.c.TestUtils.newModel(model => {
      createChromeProcessesOnModel(model);
      addInputLatencyRelatedSlices(model, 99);
      addSpaNavigationEvent(model, 100);
      addFirstPaintSlice(model, 101);

      addInputLatencyRelatedSlices(model, 198);
      addSpaNavigationEvent(model, 200);
      addFirstPaintSlice(model, 201);

      addInputLatencyRelatedSlices(model, 297);
      addSpaNavigationEvent(model, 300);
      addFirstPaintSlice(model, 301);
    });
    const histogram = getHistogramNamed(
        SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION, model);
    assert.strictEqual(3, histogram.running.count);
    const expectedDuration1 = 101 - model.convertTimestampToModelTime(
        'traceEventClock', 99 - 3 + 0.1);
    const expectedDuration2 = 201 - model.convertTimestampToModelTime(
        'traceEventClock', 198 - 3 + 0.1);
    const expectedDuration3 = 301 - model.convertTimestampToModelTime(
        'traceEventClock', 297 - 3 + 0.1);
    assert.closeTo(expectedDuration1 + expectedDuration2 + expectedDuration3,
        histogram.running.sum, 0.5);
    assert.closeTo(expectedDuration3, histogram.running.max, 0.5);
    assert.closeTo(expectedDuration1, histogram.running.min, 0.5);
  });
});
</script>
